# -*- coding: utf-8 -*-
"""
-------------------------------------------------
Project Name: luffcity study project
File Name: conn_client.py
Author: kij
Create Date: 2021/3/2
-------------------------------------------------
"""
import configparser
import hashlib
import json
import os
import struct
import subprocess

from conn import setting


class NetDataUtils(object):
	"""
	解析数据类
	"""
	# 上传数据或者发送客户端数据用到的类 ，用于统一接收和发送格式
	data_dict = {
		'action_type': '',
		'msg': '',
	}

	def __init__(self, conn):
		self.conn = conn

	def __get_head_len(self):
		"""
		读取四个字节
		:return:接下来将要读取的数据长度 整型数据
		"""
		bytes_list = []
		has_read_size = 0
		while has_read_size < 4:
			check = self.conn.recv(4 - has_read_size)
			# 如果没有数据说明断开链接
			if not check:
				return False
			has_read_size += len(check)
			bytes_list.append(check)
		header = b''.join(bytes_list)
		next_size = struct.unpack('i', header)[0]
		return next_size

	def send_msg(self, json_data):
		"""发送数据传入的数据都是json类型"""
		print('发送消息%s' % (json_data))
		# 转成二进制
		json_bytes = json_data.encode('utf-8')
		json_size = len(json_bytes)
		# 先传递json数据长度的四个字节
		self.conn.sendall(struct.pack('i', json_size))
		# 再发送json数据
		self.conn.sendall(json_bytes)

	def send_client(self, **kwargs):
		"""
		向客户端发送数据
		"""

		self.data_dict.update(kwargs)
		print(self.data_dict)
		self.send_msg(json.dumps(self.data_dict, ensure_ascii=False))

	def get_msg(self, read_size=1024):
		"""
		读取请求
		:return: 返回数据 字节数据
		"""
		next_size = self.__get_head_len()
		if not next_size:
			return False
		all_read_size = 0
		bytes_list = []
		while all_read_size < next_size:
			reads_size = read_size if next_size - all_read_size > read_size else next_size - all_read_size
			# 读取字节数据
			bytes_file = self.conn.recv(reads_size)
			# 添加到字节列表
			bytes_list.append(bytes_file)
			all_read_size += len(bytes_file)
		return b''.join(bytes_list)

	def save_file(self, file_obj, default_read_size=1024):
		"""
		保存文件
		:return:md5校验码
		"""
		print('开始保存文件===%s' % file_obj)
		next_size = self.__get_head_len()
		if not next_size:
			return False
		all_read_size = 0
		m = hashlib.md5()
		all_cum = 0
		with open(file_obj.file_path, mode='wb') as will_save_file:
			while all_read_size < next_size:
				read_size = default_read_size if next_size - all_read_size > default_read_size else next_size - all_read_size
				# 读取数据
				read_file = self.conn.recv(read_size)
				m.update(read_file)
				will_save_file.write(read_file)
				all_read_size += len(read_file)
				# 计算百分比的值
				bfb = int(round(all_read_size / next_size, 2) * 100)
				percent = "{}%".format(bfb)
				if bfb > all_cum:
					print("\r" + "#" * bfb + percent, end='', flush=True)
				all_cum = bfb
		# 返回一个md5校验码
		return m.hexdigest()

	def send_file(self, file_obj, default_read_size=1024):
		"""发送文件类型数据,接受的是一个处理好了的文件实体类"""
		print('开始发送文件')
		# 先发送四个字段文件的大小
		self.conn.sendall(struct.pack('i', file_obj.file_size))
		# 打开文件一边读一边发
		all_cum = 0
		# 空字节用于装数据
		print(file_obj.file_size)
		with open(file_obj.file_path, mode='rb') as file:
			all_ready_read_size = 0
			while all_ready_read_size < file_obj.file_size:
				read_size = default_read_size if file_obj.file_size - all_ready_read_size > default_read_size else file_obj.file_size - all_ready_read_size
				read_bytes = file.read(read_size)
				# 发送读取的文件字节
				self.conn.sendall(read_bytes)
				all_ready_read_size += len(read_bytes)
				# # 计算百分比的值
				bfb = int(round(all_ready_read_size / file_obj.file_size, 2) * 100)
				percent = "{}%".format(bfb)
				if bfb > all_cum:
					print("\r" + "#" * bfb + percent, end='', flush=True)
				all_cum = bfb

	def re_send_file(self, file_obj, all_ready_read_size, default_read_size=1024):
		"""发送文件类型数据,接受的是一个处理好了的文件实体类"""
		print('开始发送文件')
		# 先发送四个字段文件的大小  全部文件大小和已接受的文件大小相减 就是剩余的文件大小
		self.conn.sendall(struct.pack('i', file_obj.file_size - all_ready_read_size))
		# 打开文件一边读一边发
		all_cum = 0
		# 空字节用于装数据
		print(file_obj.file_size)
		print(all_ready_read_size)
		print(int(round(all_ready_read_size / file_obj.file_size, 2) * 100))
		with open(file_obj.file_path, mode='rb') as file:
			# 设置光标到已下载的文件位置
			file.seek(all_ready_read_size)

			while all_ready_read_size < file_obj.file_size:
				read_size = default_read_size if file_obj.file_size - all_ready_read_size > default_read_size else file_obj.file_size - all_ready_read_size
				read_bytes = file.read(read_size)
				# 发送读取的文件字节
				self.conn.sendall(read_bytes)
				all_ready_read_size += len(read_bytes)
				# # 计算百分比的值
				bfb = int(round(all_ready_read_size / file_obj.file_size, 2) * 100)
				percent = "{}%".format(bfb)
				if bfb > all_cum:
					print("\r" + "#" * bfb + percent, end='', flush=True)
				all_cum = bfb

	def re_save_file(self, file_obj, all_read_size, default_read_size=1024):
		"""
		保存文件
		:return:md5校验码
		"""
		next_size = self.__get_head_len()
		if not next_size:
			return False
		m = hashlib.md5()
		all_cum = 0
		with open(file_obj.file_path, mode='ab') as will_save_file:
			while all_read_size < file_obj.file_size:
				read_size = default_read_size if next_size - default_read_size > default_read_size else next_size - default_read_size
				# 读取数据
				read_file = self.conn.recv(read_size)
				m.update(read_file)
				will_save_file.write(read_file)
				all_read_size += len(read_file)
				# 计算百分比的值
				bfb = int(round(all_read_size / file_obj.file_size, 2) * 100)
				percent = "{}%".format(bfb)
				if bfb > all_cum:
					print("\r" + "#" * bfb + percent, end='', flush=True)
				all_cum = bfb

	def switch_request(self):
		"""
		分发数据，根据请求类型。然后分发数据干啥
		:return:
		"""
		# 得到json数据字典
		request_message = self.get_msg()
		if not request_message:
			return False, '无数据'
		# 解析数据
		mydata = MyDataUtils(self, request_message)
		return mydata.my_client_data_switch()


class MyFileUtils(object):
	"""文件用到的类进行处理"""

	def __init__(self, file_path=None):
		if file_path:
			self.file_path = file_path
			self.file_size = os.stat(file_path).st_size
			self.file_name = self.file_path.rsplit(os.sep, maxsplit=1)[-1]  # / 分割  \
			self.md5 = None
		else:
			self.file_path = None
			self.file_size = None
			self.file_name = None
			self.md5 = None

	def get_file_name(self, file_path):
		"""文件路径以斜杠开头取出斜杠拿到文件名"""
		filename = file_path.rsplit(os.sep, maxsplit=1)
		print(filename)
		return filename  # / 分割  \  返回文件路径和文件名

	def get_file_size(self, file_path):
		filesize = os.stat(file_path).st_size
		print('文件大小为=', filesize)
		return filesize

	def file_md5(self, default_read_size=1024):
		m = hashlib.md5()
		with open(self.file_path, mode='rb') as file:
			all_read_szie = 0
			while all_read_szie < self.file_size:
				read_size = default_read_size if self.file_size - all_read_szie > default_read_size else self.file_size - all_read_szie
				read_file = file.read(read_size)
				m.update(read_file)
				all_read_szie += len(read_file)
		self.md5 = m.hexdigest()
		print('文件md5', self.md5)
		return self.md5

	def __str__(self):
		file_dict = {
			'file_name': self.file_name,
			'file_path': self.file_path,
			'file_size': self.file_size,
			'file_md5': self.md5,
		}
		return json.dumps(file_dict)

	def read_accounts_file(self, user):
		"""
		读取服务器存储账户信息的文件
		:return:
		"""
		config = configparser.ConfigParser()
		config.read(self.file_path, encoding='utf-8')

		# 判断用户名是否存在是否存在
		if config.has_section(user.get('username')):
			# 根据节点名称得到内部的元组列表
			# 循环输出对应的元组

			m = hashlib.md5(user.get('password').encode('utf-8'))
			if config.get(user.get('username'), 'password') == m.hexdigest():
				return setting.SUCCESS, '用户名和密码正确'
			else:
				return setting.ERROR, '用户名密码不正确'
		else:
			return setting.ERROR, '用户名不存在'

	@classmethod
	def get_user_index_path(cls, user):
		return os.path.join(setting.USER_HOME_DIR, user.get('username'))


class MyDataUtils(object):
	"""数据分析，拆分"""

	def __init__(self, conn, data):
		# 接收的数据是字节数据 字节转换成utf-8类型json然后变成字典类型的data
		self.data = json.loads(data.decode('utf-8'))
		# 拿到服务器链接对象进行发送数据
		self.send = conn

	def _logon(self, data=None):
		# 这里需要对比服务器中存储用户名和密码的文件
		file_utils = MyFileUtils(setting.ACCOUNT_FILE)
		code, hint = file_utils.read_accounts_file(data)
		self.send.send_client(
			action_type=data.get('action_type'),
			code=code, msg=hint,
			username=data.get('username'), current_dir='/'
		)

	def _update_file(self, data=None):
		print('上传文件需要解析的数据===%s' % data)
		# 拿到文件路径 文件当前相对路径
		file_path = os.path.join(MyFileUtils.get_user_index_path(data), data.get('current_dir')[1:],
								 data.get('file_name'))
		file_utils = MyFileUtils()
		file_utils.file_path = file_path
		file_utils.file_size = data.get('file_size')
		file_utils.file_name = data.get('file_name')
		file_utils.md5 = data.get('file_md5')
		print(str(file_utils))
		self.send.save_file(file_utils)
		self.send.send_client(
			action_type=data.get('action_type'),
			code=setting.SUCCESS, msg='上传完成',
			data=str(file_utils)
		)

	def _down_file(self, data=None):
		file_path = os.path.join(
			MyFileUtils.get_user_index_path(data), data.get('current_dir')[1:],
			data.get('file_path')[1:] if str(data.get('file_path')).startswith(os.sep) else data.get('file_path'))

		# 文件存在
		if os.path.isfile(file_path):
			file_utils = MyFileUtils()
			file_utils.file_path = file_path
			file_utils.file_name = data.get('file_name')
			file_utils.file_size = file_utils.get_file_size(file_utils.file_path)
			file_utils.md5 = file_utils.file_md5()
			file_utils.file_path = file_path.replace(MyFileUtils.get_user_index_path(data), '')
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.SUCCESS, msg='开始下载',
				data=str(file_utils)
			)
			file_utils.file_path = file_path
			self.send.send_file(file_utils)
		else:
			# 文件不存在
			self.send.send_client(action_type=data.get('action_type'), code=setting.ERROR, msg='文件不存在')

	def _ls(self, data=None):
		"""显示文件夹"""
		'''显示家目录路径文件,运用subprocess类库'''

		data_folder = os.path.join(MyFileUtils.get_user_index_path(data), data.get('current_dir')[1:])
		cmd_obj = subprocess.Popen(
			"""ls \"%s\"""" % data_folder,
			shell=True, stdout=subprocess.PIPE,
			stderr=subprocess.PIPE)

		stdout = cmd_obj.stdout.read()
		stderr = cmd_obj.stderr.read()
		cmd_result = stdout + stderr

		if len(stderr) > 0:
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.ERROR, msg=cmd_result.decode('utf-8'))
		else:
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.SUCCESS,
				msg='当前文件夹下内容是:' if len(cmd_result.decode('utf-8')) > 0 else '当前文件夹没内容',
				data=cmd_result.decode('utf-8'))

	def _dir(self, data=None):
		"""显示文件夹"""
		'''显示家目录路径文件,运用subprocess类库'''

		data_folder = os.path.join(MyFileUtils.get_user_index_path(data), data.get('current_dir')[1:])

		cmd_obj = subprocess.Popen(
			"""dir \"%s\"""" % data_folder,
			shell=True, stdout=subprocess.PIPE,
			stderr=subprocess.PIPE)

		stdout = cmd_obj.stdout.read()
		stderr = cmd_obj.stderr.read()
		cmd_result = stdout + stderr
		if len(stderr) > 0:
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.ERROR, msg=cmd_result.decode('utf-8'))
		else:
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.SUCCESS,
				msg='当前文件夹下内容是:' if len(cmd_result.decode('utf-8')) > 0 else '当前文件夹没内容',
				data=cmd_result.decode('utf-8'))

	def _cd(self, data=None):
		"""切换文件夹"""
		print(data.get('file_path'))
		data_folder = os.path.abspath(
			os.path.join(
				MyFileUtils.get_user_index_path(data),
				data.get('current_dir')[1:],
				data.get('file_path')))
		print(data_folder)
		if len(data_folder) <= len(MyFileUtils.get_user_index_path(data)):
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.SUCCESS, msg='当前文件目录为你的根目录',
				current_dir='/',
				data=data.get('file_path'))
		elif os.path.exists(data_folder):
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.SUCCESS,
				msg='当前文件目录为:%s' % data_folder.replace(MyFileUtils.get_user_index_path(data), ''),
				current_dir=data_folder.replace(MyFileUtils.get_user_index_path(data), ''),
				data=data.get('file_path'))
		else:
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.ERROR, msg='%s路径不存在' % data.get('file_path'),
			)

	def _mkdir(self, data=None):
		"""创建文件夹"""
		'''显示家目录路径文件,运用subprocess类库'''
		folder_path = os.path.join(MyFileUtils.get_user_index_path(data), data.get('current_dir')[1:],
								   data.get('file_path'))
		if not os.path.exists(folder_path):
			os.makedirs(folder_path)
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.SUCCESS, msg='文件夹%s创建成功' % data.get('file_path')
			)
		else:
			self.send.send_client(action_type=data.get('action_type'), code=setting.ERROR, msg='文件已存在')

	def _re_get(self, data=None):
		"""断点续传"""
		folder_path = os.path.join(
			# 登录根目录
			MyFileUtils.get_user_index_path(data),
			data.get('current_dir')[1:],
			data.get('file_path')[1:] if str(data.get('file_path')).startswith(os.sep) else data.get('file_path')
		)

		print(folder_path)
		if os.path.exists(folder_path):
			# 发送文件 现将文件定位到已下载的未知 文件对象 ， 已下载文件大小 already_received_size，
			file_utils = MyFileUtils()
			file_utils.file_path = folder_path
			file_utils.file_name = file_utils.get_file_name(file_utils.file_path)[-1]
			file_utils.file_size = file_utils.get_file_size(file_utils.file_path)
			file_utils.md5 = file_utils.file_md5()
			file_utils.file_path = folder_path.replace(MyFileUtils.get_user_index_path(data), '')
			self.send.send_client(
				action_type=data.get('action_type'),
				code=setting.SUCCESS, msg='继续下载',
				data=str(file_utils)
			)
			file_utils.file_path = folder_path
			self.send.re_send_file(file_utils, data.get('already_received_size'))
		else:
			self.send.send_client(action_type=data.get('action_type'), code=setting.ERROR, msg='文件不存在了')

	func_dict = {
		'logon': _logon,
		'down_file': _down_file,
		'ls': _ls,
		'cd': _cd,
		'dir': _dir,
		'mkdir': _mkdir,
		're_get': _re_get,
		'update_file': _update_file
	}

	def my_client_data_switch(self):
		"""
		处理用户端数据上传逻辑
		1. 用户加密认证
		2. 允许多用户登录
		3. 每个用户都有自己的家目录，且只能访问自己的家目录
		:return:
		"""
		# 拆分数据{'action_type': 'logon', 'username': 'kijlee', 'password': '123123'}
		print(self.data)
		if hasattr(self, "_%s" % self.data.get('action_type')):
			func = getattr(self, "_%s" % self.data.get('action_type'))
			func(self.data)
			return True, '%s方法执行完毕' % self.data.get('action_type')
		else:
			return False, '方法错误'
